// Windows 系统的直接 IO 实现

package turbocopy

import (
	"os"
	"syscall"
	"unicode/utf16"
)

const (
	// 缓冲区对齐的大小
	AlignSize = 4096

	// 最小块大小
	BlockSize = 4096

	// Windows 系统的额外标志
	FILE_FLAG_NO_BUFFERING  = 0x20000000
	FILE_FLAG_WRITE_THROUGH = 0x80000000
)

// utf16FromString 返回 UTF-8 字符串 s 的 UTF-16 编码，
// 并添加终止 NUL。如果 s 在任何位置包含 NUL 字节，
// 则返回 (nil, EINVAL)。
//
// 注意：从 Go 源代码复制而来
func utf16FromString(s string) ([]uint16, error) {
	for i := 0; i < len(s); i++ {
		if s[i] == 0 {
			return nil, syscall.EINVAL
		}
	}
	return utf16.Encode([]rune(s + "\x00")), nil
}

// OpenFile 是 os.OpenFile 的修改版本，
// 向 Windows CreateFile 传递以下标志。
//
// FILE_FLAG_NO_BUFFERING 进一步扩展了这个概念，
// 消除了所有预读文件缓冲和磁盘缓存，
// 因此所有读取都保证来自文件而不是任何系统缓冲区或磁盘缓存。
// 当使用 FILE_FLAG_NO_BUFFERING 时，
// 磁盘读写必须在扇区边界上进行，
// 并且缓冲区地址必须在内存中按磁盘扇区边界对齐。
//
// 注意：从 Go 源代码复制并修改
func OpenFile(path string, mode int, perm os.FileMode) (file *os.File, err error) {
	if len(path) == 0 {
		return nil, &os.PathError{Op: "open", Path: path, Err: syscall.ERROR_FILE_NOT_FOUND}
	}
	pathp, err := utf16FromString(path)
	if err != nil {
		return nil, &os.PathError{Op: "open", Path: path, Err: err}
	}
	var access uint32
	switch mode & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) {
	case os.O_RDONLY:
		access = syscall.GENERIC_READ
	case os.O_WRONLY:
		access = syscall.GENERIC_WRITE
	case os.O_RDWR:
		access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
	}
	if mode&syscall.O_CREAT != 0 {
		access |= syscall.GENERIC_WRITE
	}
	if mode&os.O_APPEND != 0 {
		access &^= syscall.GENERIC_WRITE
		access |= syscall.FILE_APPEND_DATA
	}
	sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE)
	var sa *syscall.SecurityAttributes
	var createmode uint32
	switch {
	case mode&(syscall.O_CREAT|os.O_EXCL) == (syscall.O_CREAT | os.O_EXCL):
		createmode = syscall.CREATE_NEW
	case mode&(syscall.O_CREAT|os.O_TRUNC) == (syscall.O_CREAT | os.O_TRUNC):
		createmode = syscall.CREATE_ALWAYS
	case mode&syscall.O_CREAT == syscall.O_CREAT:
		createmode = syscall.OPEN_ALWAYS
	case mode&os.O_TRUNC == os.O_TRUNC:
		createmode = syscall.TRUNCATE_EXISTING
	default:
		createmode = syscall.OPEN_EXISTING
	}
	h, e := syscall.CreateFile(&pathp[0], access, sharemode, sa, createmode, syscall.FILE_ATTRIBUTE_NORMAL|FILE_FLAG_NO_BUFFERING|FILE_FLAG_WRITE_THROUGH, 0)
	if e != nil {
		return nil, &os.PathError{Op: "open", Path: path, Err: e}
	}
	return os.NewFile(uintptr(h), path), nil
}
